/**
 * Copyright 厦门中软海晟信息技术有限公司 版权所有 违者必究 2019
 */
package com.opencvjava.code.sightReader;

import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfInt;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.RotatedRect;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.HOGDescriptor;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

import static com.opencvjava.support.util.CvUtils.imshow;
import static com.opencvjava.support.util.CvUtils.mat;
import static org.opencv.core.Core.BORDER_CONSTANT;
import static org.opencv.imgcodecs.Imgcodecs.IMREAD_GRAYSCALE;
import static org.opencv.imgproc.Imgproc.CHAIN_APPROX_SIMPLE;
import static org.opencv.imgproc.Imgproc.Canny;
import static org.opencv.imgproc.Imgproc.HoughLinesP;
import static org.opencv.imgproc.Imgproc.INTER_LANCZOS4;
import static org.opencv.imgproc.Imgproc.RETR_EXTERNAL;
import static org.opencv.imgproc.Imgproc.RETR_TREE;
import static org.opencv.imgproc.Imgproc.THRESH_BINARY;
import static org.opencv.imgproc.Imgproc.drawContours;
import static org.opencv.imgproc.Imgproc.findContours;
import static org.opencv.imgproc.Imgproc.getRotationMatrix2D;
import static org.opencv.imgproc.Imgproc.threshold;
import static org.opencv.imgproc.Imgproc.warpAffine;

/**
 * @author : sunzb
 * @date: 2019/12/2
 */
@Service
public class SightReader {

    private final Random rng = new Random();

    public void test() {
        Mat gray = mat("piano/sightReader", "test.jpg", IMREAD_GRAYSCALE);
        if (gray.empty()) throw new RuntimeException("源图读取失败");
        imshow("源图", gray);
        Mat edges = new Mat();
        Canny(gray, edges, 50, 200, 3);
        double angle = findRotationAngle(edges);
        Mat r = rotate(gray, angle);
        imshow("校正", r);
        Mat bin = new Mat();
        threshold(r, bin, 100, 255, THRESH_BINARY);
        imshow("二值化", bin);
        byte[] dataBin = new byte[(int) bin.total()];
        bin.get(0, 0, dataBin);
        List<Integer> horizProj = horizProj(dataBin, bin.rows(), bin.cols());
        List<Integer> vertProj = vertProj(dataBin, bin.rows(), bin.cols());
        List<Integer> staffPositions;
        int horizMax = max(horizProj);
        for (int i = 0; i < horizProj.size(); i++) {
            int horiz = horizProj.get(i);
            if (horiz > horizMax / 8.0)
                removeStaff(dataBin, bin.rows(), bin.cols(), i);
        }
        Mat matNoLine = new Mat(bin.size(), bin.type());
        matNoLine.put(0, 0, dataBin);
        imshow("去谱线", matNoLine);
        List<MatOfPoint> contours = new ArrayList<>();
        Mat hierarchy = new Mat();
        findContours(bin, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
        Mat drawing = Mat.zeros(bin.size(), CvType.CV_8UC3);
        List<MatOfPoint> hullList = new ArrayList<>();
        // 从contour中取凸包顶点
        for (MatOfPoint contour : contours) {
            MatOfInt hull = new MatOfInt();
            Imgproc.convexHull(contour, hull);
            Point[] contourArray = contour.toArray();
            Point[] hullPoints = new Point[hull.rows()];
            List<Integer> hullContourIdxList = hull.toList();
            for (int i = 0; i < hullContourIdxList.size(); i++) {
                hullPoints[i] = contourArray[hullContourIdxList.get(i)];
            }
            hullList.add(new MatOfPoint(hullPoints));
        }
        for (int i = 0; i < contours.size(); i++) {
            Scalar color = new Scalar(rng.nextInt(256), rng.nextInt(256), rng.nextInt(256));
            drawContours(drawing, contours, i, color);
            drawContours(drawing, hullList, i, color);
        }
        imshow("轮廓", drawing);
//        HOGDescriptor hog;

    }

    // TODO: 2019/12/2
    private void removeStaff(byte[] dataBin, int rows, int cols, int row) {
        int curRow, rowIndex = row * cols;
        for (int col = 0; col < cols; col++) {
            if (dataBin[rowIndex + col] == 0) {
                int sum = 0;
                for (int rowDelta = -3; rowDelta <= 3; rowDelta++) {
                    curRow = row + rowDelta;
                    if (curRow > 0 && curRow < rows)
                        sum += dataBin[curRow * cols + col] & 0xff;
                }
                if (sum > 1000) {
                    for (int rowDelta = -2; rowDelta <= 2; rowDelta++) {
                        curRow = row + rowDelta;
                        if (curRow > 0 && curRow < rows)
                            dataBin[curRow * cols + col] = -1;
                    }
                }
            }
        }
    }

    private int max(List<Integer> list) {
        int max = Integer.MIN_VALUE;
        for (Integer i : list) if (max < i) max = i;
        return max;
    }

    private List<Integer> horizProj(byte[] dataBin, int rows, int cols) {
        List<Integer> horizPorj = new ArrayList<>();
        int count, row, col, rowIndex;
        for (row = 0; row < rows; row++) {
            rowIndex = row * cols;
            for (count = 0, col = 0; col < cols; col++)
                count += dataBin[rowIndex + col] == 0 ? 1 : 0;
            horizPorj.add(count);
        }
        return horizPorj;
    }

    private List<Integer> vertProj(byte[] dataBin, int rows, int cols) {
        List<Integer> vertProj = new ArrayList<>();
        int count, row, col;
        for (col = 0; col < cols; col++) {
            for (count = 0, row = 0; row < rows; row++)
                count += dataBin[row * cols + col] == 0 ? 1 : 0;
            vertProj.add(count);
        }
        return vertProj;
    }

    private Mat rotate(Mat gray, double angle) {
        Point pt = new Point(gray.cols() / 2.0, gray.rows() / 2.0);
        Mat r = getRotationMatrix2D(pt, angle, 1.0);
        Rect bbox = new RotatedRect(pt, gray.size(), angle).boundingRect();
        r.put(0, 2, bbox.width/2.0 - pt.x);
        r.put(1, 2, bbox.height/2.0 - pt.y);
        Mat rotate = new Mat();
        warpAffine(gray, rotate, r, bbox.size(), INTER_LANCZOS4, BORDER_CONSTANT, new Scalar(255, 255, 255));
        return rotate;
    }

    private double findRotationAngle(Mat edges) {
        Mat linesP = new Mat();
        HoughLinesP(edges, linesP, 1, Math.PI / 180, 100, 50, 10);
        List<Double> slopes = new ArrayList<>();
        if(linesP.rows() < 3) throw new RuntimeException("找不到3条直线");
        for (int row = 0; row < linesP.rows(); row++) {
            double[] l = linesP.get(row, 0);
            slopes.add((Math.abs(l[0] - l[2]) < 0.0000001) ? 1000000 : ((double) (l[1]-l[3])) / (l[0]-l[2]));
        }
        Collections.sort(slopes);
        return Math.atan(slopes.get(slopes.size() / 2)) * 180.0/Math.PI;
    }

    public static void main(String[] args) {
        System.out.println(1 / 2);
        System.out.println(1 / 2.0);
        System.out.println(((double)1) / 2);
    }
}
